/*MyPhotoIndex Free Photo Organizer <www.myphotoindex.com>
  Copyright (C) <2006-2009>  <Asaf Yarkoni asaf@yarkoni.net>

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using MyPhotoIndex.DataAccess;
using System.Drawing;
using System.IO;
using MyPhotoIndex.Utilities;
using System.Collections.ObjectModel;
using MyPhotoIndex.Utilities.ImageMetaData;
using MyPhotoIndex.Interfaces;
using MyPhotoIndex.Plugins.ImageFormatProviders;
using MyPhotoIndex.ImageBank;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using MyPhotoIndex.Interfaces.Imaging;
using MyPhotoIndex.Interfaces.ImageFormatProviders;
using MyPhotoIndex.Interfaces.Tagging;

namespace MyPhotoIndex.Importer
{
    public abstract class ImageDataImporter
    {
        public event EventHandler<ImageDataImporterEventArgs> ImageDataImporterProcessEvent;
        public event EventHandler<SetImageTagsEventArgs> SetImageTagsEvent;

        private StringCollection m_fileList;
        private List<ImageImportException> m_errors;
        private int m_currentFileIndex;
        private string m_importBatch;
        private string m_centralStoragePath;
        private bool m_useCentralStorage;
        private string m_timeStamp;
        private Semaphore m_semaphore = null;
        private readonly int NumberOfImportThreads;
        private bool m_stop;

        private Object m_syncLock = new object();

        protected ImageDataImporter(string importBatch)
        {
            m_fileList = new StringCollection();
            m_errors = new List<ImageImportException>();
            m_importBatch = importBatch;

            DateTime cd = DateTime.Now;
            object[] dateParts = new object[] { cd.Year, cd.Month, cd.Day, cd.Hour, cd.Minute, cd.Second };
            m_timeStamp = string.Format("{0}-{1}-{2}-{3}{4}-{5}", dateParts);

            NumberOfImportThreads = Environment.ProcessorCount * 3;
            m_semaphore = new Semaphore(NumberOfImportThreads, NumberOfImportThreads);
        }

        protected StringCollection FileList
        {
            get { return m_fileList; }
        }

        public ReadOnlyCollection<ImageImportException> Errors
        {
            get { return m_errors.AsReadOnly(); }
        }

        protected void Import(Size thumbnailSize)
        {
            m_currentFileIndex = 0;
            m_errors.Clear();
            m_stop = false;

            using (MyPhotoIndexDataSet.ImagesDataTable dataTable = new MyPhotoIndexDataSet.ImagesDataTable())
            {
                foreach (string fileName in m_fileList)
                {
                    try
                    {
                        m_semaphore.WaitOne();
                        ThreadPool.QueueUserWorkItem(new WaitCallback(ImportOneImageFile),
                                new ImportDataPackage(dataTable, fileName, thumbnailSize));

                        if (m_stop == true)
                        {
                            return;
                        }
                    }
                    catch (Exception ex)
                    {
                        m_errors.Add(new ImageImportException(fileName, ex));
                    }

                    m_currentFileIndex++;
                }

                for (int i = 0; i < NumberOfImportThreads; i++) m_semaphore.WaitOne();

                foreach (MyPhotoIndexDataSet.ImagesRow row in dataTable)
                {
                    if (row.HasErrors == true)
                    {
                        m_errors.Add(new ImageImportException(row.FullPath, row.RowError));
                    }
                }
            }
        }

        internal void ImportOneImageFile(Object importData)
        {
            ImportDataPackage importDataPackage = (ImportDataPackage)importData;
            string fileName = importDataPackage.FileName;
            MyPhotoIndexDataSet.ImagesDataTable dataTable = importDataPackage.DataTable;
            Size thumbnailSize = importDataPackage.ThumbnailSize;

            string origFileName = fileName;
            fileName = ProcessFileBeforeCopy(fileName);

            IImageData imageData = Storage.Instance.FindByPath(fileName);
            if (imageData != null)
            {
                m_semaphore.Release();
                return;
            }

            Image thumbnail = null;
            IImageMetaData imageMetaData = null;
            IImageFormatProvider provider = null;

            try
            {
                provider = ImageFormatProviders.Default[Path.GetExtension(fileName)];
                imageMetaData = provider.GetImageMetaData(fileName);

                thumbnail = provider.GetThumbnail(fileName, thumbnailSize);
            }
            catch (ArgumentException)
            {
                thumbnail = null;
            }

            try
            {
                using (thumbnail)
                {
                    DateTime fileCreationDate = File.GetCreationTime(fileName);

                    if (imageMetaData.DateTaken == DateTime.MinValue)
                    {
                        imageMetaData.DateTaken = File.GetLastWriteTime(fileName);
                    }

                    if (ImageDataImporterProcessEvent != null)
                    {
                        ImageDataImporterEventArgs die = new ImageDataImporterEventArgs(origFileName, thumbnail, m_currentFileIndex);
                        ImageDataImporterProcessEvent.Invoke(this, die);
                        if (die.Stop == true)
                        {
                            m_stop = true;
                            m_semaphore.Release();
                            return;
                        }
                    }

                    string storedFileName = fileName;
                    if (m_useCentralStorage == true)
                    {
                        storedFileName = fileName.Remove(0, m_centralStoragePath.Length + 1);
                    }


                    byte[] imageTags = ImportImagetags(imageMetaData);

                    //String fileSignature = String.Empty;
                    //try
                    //{
                    //    fileSignature = provider.GetImageMediaSignature(fileName);
                    //}
                    //catch(Exception ex)
                    //{
                    //    Logger.Log(ex);
                    //}

                    int rowId = -1;
                    lock (m_syncLock)
                    {
                        MyPhotoIndexDataSet.ImagesRow newRow = dataTable.AddImagesRow(m_importBatch, storedFileName, fileCreationDate, imageMetaData.ImageDescription,
                                                imageMetaData.CameraMaker, imageMetaData.CameraModel, (short)-1,
                                                (short)-1, (short)-1, string.IsNullOrEmpty(imageMetaData.SoftwareUsed) ? null : imageMetaData.SoftwareUsed.Substring(0, Math.Min(50, imageMetaData.SoftwareUsed.Length)),
                                                imageMetaData.LastModifiedDateTime, 0/*imageData.ExposureTime*/, imageMetaData.Aperture,
                                                -1, (short)imageMetaData.ExposureProgram, imageMetaData.IsoSpeed,
                                                imageMetaData.DateTaken, imageMetaData.DateDigitized, -1,
                                                imageMetaData.ExposureCompensation, (short)imageMetaData.MeteringMode, (short)imageMetaData.LightSource,
                                                (short)imageMetaData.FlashStatus, imageMetaData.FocalLength,
                                                (short)imageMetaData.ImageWidth, (short)imageMetaData.ImageHeight, imageTags, 0, 0, -1, DateTime.MinValue, string.Empty);

                        DataAccessProvider.Instance.UpdateDataTable(dataTable, true, true);
                        rowId = newRow.Id;
                    }

                    if (thumbnail != null)
                    {
                        Storage.Instance.AddCahcedThumbnail(rowId, thumbnail);
                    }
                }
            }
            catch (Exception ex)
            {
                m_errors.Add(new ImageImportException(fileName, ex));
            }

            m_semaphore.Release();
        }

        protected string ProcessFileBeforeCopy(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
            {
                return fileName;
            }

            if (m_useCentralStorage == true)
            {
                string centralStorageRoot = ApplicationFolders.CentralStorageLocation;
                if (string.IsNullOrEmpty(centralStorageRoot) == false)
                {
                    if (fileName.StartsWith(centralStorageRoot, StringComparison.CurrentCultureIgnoreCase) == false)
                    {
                        string downloadPath = Path.Combine(centralStorageRoot, m_timeStamp);

                        Directory.CreateDirectory(downloadPath);

                        string newFileName = Path.Combine(downloadPath, Path.GetFileName(fileName));
                        File.Copy(fileName, newFileName);
                        fileName = newFileName;
                    }
                }
            }

            return fileName;
        }

        protected virtual byte[] ImportImagetags(IImageMetaData imageMetaData)
        {
            List<int> imageTags = new List<int>();

            String importedTagsCategoryName = "Imported tags";

            IImageTagCollection tagFactory = ImageTagCollection.Instance;
            IImageTag importedTagsCategory = tagFactory.GetTagByPath(importedTagsCategoryName);
            if (importedTagsCategory == null)
            {
                lock (m_syncLock)
                {
                    importedTagsCategory = tagFactory.GetTagByPath(importedTagsCategoryName);
                    if (importedTagsCategory == null)
                    {
                        importedTagsCategory = tagFactory.Create(null, importedTagsCategoryName);
                        tagFactory.SaveTag(importedTagsCategory);
                    }
                }
            }

            if (imageMetaData.Keywords.Count > 0)
            {
                foreach (String keyword in imageMetaData.Keywords)
                {
                    IImageTag newTag = tagFactory.GetTagByKeyword(keyword);
                    if (newTag == null)
                    {
                        lock (m_syncLock)
                        {
                            newTag = tagFactory.GetTagByKeyword(keyword);
                            if (newTag == null)
                            {
                                newTag = tagFactory.Create(importedTagsCategory, keyword);
                                newTag.Keyword = keyword;
                                tagFactory.SaveTag(newTag);
                                imageTags.Add(newTag.Id);
                            }
                        }
                    }
                    else
                    {
                        imageTags.Add(newTag.Id);
                    }
                }
            }

            try
            {
                if (SetImageTagsEvent != null)
                {
                    lock (m_syncLock)
                    {
                        SetImageTagsEventArgs args = new SetImageTagsEventArgs(imageMetaData);
                        SetImageTagsEvent.Invoke(this, args);
                        if (args.ImageTags.Count > 0)
                        {
                            foreach (IImageTag tagInfo in args.ImageTags)
                            {
                                imageTags.Add(tagInfo.Id);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }

            if (imageTags.Count == 0)
            {
                return new byte[] { };
            }

            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(ms, imageTags.ToArray());
                return ms.ToArray();
            }
        }

        public string CentralStoragePath
        {
            get { return m_centralStoragePath; }
            set { m_centralStoragePath = value; }
        }

        public bool UseCentralStorage
        {
            get { return m_useCentralStorage; }
            set { m_useCentralStorage = value; }
        }
    }
}
